package com.boarsoft.boar.agent;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.SQLExec;
import org.apache.tools.ant.types.EnumeratedAttribute;
import org.codehaus.plexus.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;

import com.boarsoft.agent.AgentService;
import com.boarsoft.bean.ReplyInfo;
import com.boarsoft.common.Util;
import com.boarsoft.common.util.FileUtil;
import com.boarsoft.common.util.StreamUtil;
import com.boarsoft.common.util.ZipUtil;
import com.boarsoft.flow.core.SimpleFlow;
import com.boarsoft.flow.core.SimpleFlowEngine;
import com.boarsoft.rpc.core.RpcContext;

public class AgentServiceImpl implements AgentService {
	private static final Logger log = LoggerFactory.getLogger(AgentServiceImpl.class);

	@Autowired
	protected SimpleFlowEngine simpleFlowEngine;

	@Autowired
	@Lazy(value = true)
	@Qualifier("agentX")
	protected AgentService agentX;

	protected boolean windows;
	
	protected String charset = StandardCharsets.UTF_8.name();

	public AgentServiceImpl() {
		String os = System.getProperty("os.name", "unknown");
		windows = os.toLowerCase().startsWith("win");
	}

	@Override
	@Deprecated
	public ReplyInfo<Object> executeSql(String dbId, String path, String taskKey)
			throws UnsupportedEncodingException, IOException {

		String driver = AgentConfig.getProperty(dbId.concat(".driver"));
		String url = AgentConfig.getProperty(dbId.concat(".url"));
		// String port = AgentConfig.getProperty(dbId.concat(".port"));
		String username = AgentConfig.getProperty(dbId.concat(".username"));
		String password = AgentConfig.getProperty(dbId.concat(".password"));

		log.debug("{}.driver={}", dbId, driver);
		log.debug("{}.url={}", dbId, url);
		log.debug("{}.username={}", dbId, username);
		log.debug("{}.password={}", dbId, password);

		SQLExec sqlExec = new SQLExec();
		sqlExec.setDriver(driver);
		sqlExec.setUrl(url);
		sqlExec.setUserid(username);
		sqlExec.setPassword(password);

		String fp = AgentConfig.FILE_PATH.concat(path);
		log.info("Read and execute SQL file {}", fp);
		sqlExec.setSrc(new File(fp));
		sqlExec.setOnerror((SQLExec.OnError) (EnumeratedAttribute.getInstance(SQLExec.OnError.class, "abort")));
		sqlExec.setPrint(true); // 设置是否输出
		sqlExec.setOutput(new File(fp.concat(".log")));
		sqlExec.setProject(new Project()); // 要指定这个属性，不然会出错
		sqlExec.setEncoding("UTF-8");
		try {
			sqlExec.execute();
		} catch (Exception e) {
			log.error("Error on execute SQL file {}", fp, e);
			return new ReplyInfo<Object>(e.getMessage());
		}
		return new ReplyInfo<Object>();
	}

	@Deprecated
	public ReplyInfo<Object> executeSql2(String dbId, String path) throws UnsupportedEncodingException, IOException {
		//
		String driver = AgentConfig.getProperty(dbId.concat(".driver"));
		String url = AgentConfig.getProperty(dbId.concat(".url"));
		// String port = AgentConfig.getProperty(dbId.concat(".port"));
		String username = AgentConfig.getProperty(dbId.concat(".username"));
		String password = AgentConfig.getProperty(dbId.concat(".password"));

		String fp = AgentConfig.FILE_PATH.concat(path);
		String content = FileUtil.read(new File(fp), "UTF-8");
		String[] sqlArr = content.split("\n\n\n");
		// int pc = (sqlArr.length + AgentConfig.SQL_PAGE_SIZE - 1)
		// / AgentConfig.SQL_PAGE_SIZE;
		int pc = (sqlArr.length + 99) / 100;
		int start = -1;
		int end = -1;
		Connection conn = null;
		Statement stmt = null;

		try {
			Class.forName(driver);
			conn = DriverManager.getConnection(url, username, password);
			conn.setAutoCommit(false);
			stmt = conn.createStatement();
			//
			for (int p = 0; p < pc; p++) {
				start = p * 100;
				end = Math.min(start + 100, sqlArr.length);
				for (int i = start; i < end; i++) {
					stmt.addBatch(sqlArr[i]);
				}
				try {
					int[] ra = stmt.executeBatch();
					conn.commit();
					for (int i = start; i < end; i++) {
						log.info("{}: {} affected {} rows", path, sqlArr[i], ra[i - start]);
					}
				} catch (Exception e) {
					conn.rollback();
					for (int i = start; i < end; i++) {
						log.error("{}: {} failed because {}", path, sqlArr[i], e.getMessage());
					}
					throw e;
				}
			}
			return new ReplyInfo<Object>();
		} catch (Exception e) {
			String s = new StringBuilder().append("Error on execute ").append(path).append(", line ").append(start).append("~")
					.append(end).append(", because ").append(e.getMessage()).toString();
			log.error(s, e);
			return new ReplyInfo<Object>(s);
		} finally {
			if (stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					log.error("Can not close statment", e);
				}
			}
			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					log.error("Can not close connection", e);
				}
			}
		}
	}

	@Override
	@Deprecated
	public ReplyInfo<Object> executeShell(String path, String[] args, String taskKey) throws IOException, InterruptedException {
		return this.executeShell(AgentConfig.FILE_PATH, path, args, taskKey);
	}

	@Override
	@Deprecated
	public ReplyInfo<Object> executeShell(String user, String addr, String parent, String path, String[] args, String taskKey)
			throws IOException, InterruptedException {
		log.info("Execute {} with {}", path, args);
		StringBuilder sb = new StringBuilder();
		sb.append("sh ").append(parent).append("/").append(path);
		if (args != null) {
			for (String a : args) {
				sb.append(" ").append(a);
			}
		}
		String cmd = sb.toString();
		return this.executeShell2(user, addr, cmd, taskKey);
	}

	@Override
	@Deprecated
	public ReplyInfo<Object> executeShell(String parent, String path, String[] args, String taskKey)
			throws IOException, InterruptedException {
		log.info("Execute {} with {}", path, args);
		StringBuilder sb = new StringBuilder();
		sb.append("sh ").append(parent).append("/").append(path);
		if (args != null) {
			for (String a : args) {
				sb.append(" ").append(a);
			}
		}
		String cmd = sb.toString();
		return this.executeShell(cmd);
		// String[] cmds = new String[args.length + 3];
		// cmds[0] = "sh";
		// cmds[1] = "-c";
		// cmds[2] = new
		// StringBuilder(parent).append("/").append(path).toString();
		// for (int i = 0; i < args.length; i++) {
		// cmds[i + 3] = args[i];
		// }
		// return this.executeShell(cmds);
	}

	@Override
	@Deprecated
	public ReplyInfo<Object> executeCmd(String code, String[] args, String taskKey) throws IOException, InterruptedException {
		log.info("Execute {} with {}", code, args);
		StringBuilder sb = new StringBuilder();
		sb.append("sh ").append(AgentConfig.SHELL_PATH)//
				.append("/").append(code).append(".sh");
		if (args != null) {
			for (String a : args) {
				sb.append(" ").append(a);
			}
		}
		String cmd = sb.toString();
		return this.executeShell(cmd);
	}

	@Override
	public ReplyInfo<Object> executeShell2(String user, String addr, String cmd, String taskKey)
			throws IOException, InterruptedException {
		String to = new StringBuilder(user).append("@").append(addr).toString();
		log.debug("Remote execute shell: [ssh {} '{}']", to, cmd);
		return this.executeShell(new String[] { "ssh", to, cmd });
	}

	/**
	 * 指定给定的SHELL
	 *
	 * @param cmds
	 * @return
	 * @throws IOException
	 * @throws InterruptedException
	 */
	@Override
	public ReplyInfo<Object> executeShell(String[] cmds) throws IOException, InterruptedException {
		// if (cmds == null || cmds.length < 1) {
		// throw new IllegalArgumentException("Command can not be empty");
		// }
		log.info("Execute commands:");
		for (int i = 0; i < cmds.length; i++) {
			log.info("{}={}", i, cmds[i]);
		}
		Process process = null;
		try {
			process = Runtime.getRuntime().exec(cmds);
			// 读取并处理shell的标准输出
			ProcessStreamReader sr = new ProcessStreamReader(process.getInputStream(), charset);
			Thread st = new Thread(sr);
			st.start();
			// 读取并处理shell的错误输出
			ProcessStreamReader er = new ProcessStreamReader(process.getErrorStream(), charset);
			Thread et = new Thread(er);
			et.start();
			//
			st.join();
			et.join();
			// r==0表示正常退出，其它表示异常退出或进程不存在，抛异常表示失败
			int r = process.waitFor();
			if (Util.strIsEmpty(er.getResult())) {
				log.info("Shell [{}] exited with code {}", cmds, r);
				return new ReplyInfo<Object>();
			}
			return new ReplyInfo<Object>(er.getResult());
		} finally {
			if (process != null) {
				try {
					process.destroy();
				} catch (Exception e) {
					log.error("Error on destroy process", e);
				}
			}
		}
	}

	@Override
	public ReplyInfo<Object> start(String flowId, Object data) throws Throwable {
		log.info("Start flow {}", flowId);
		SimpleFlow flow = simpleFlowEngine.start(flowId, data);
		log.info("Flow {} completed", flow);
		return new ReplyInfo<Object>();
	}

	@Override
	public ReplyInfo<Object> copy2(String from, String to) throws Throwable {
		File sf = new File(from);
		File tf = new File(to);
		if (!sf.exists()) {
			return new ReplyInfo<Object>(new StringBuilder().append("File path")//
					.append(from).append(" does not exists").toString());
		}
		if (!tf.exists() && !FileUtil.makePath(to)) {
			return new ReplyInfo<Object>("Can not mkdir ".concat(to));
		}
		if (sf.isDirectory()) {
			FileUtils.copyDirectory(sf, tf);
		}
		if (sf.isFile()) {
			if (tf.isDirectory()) {
				FileUtils.copyFileToDirectory(sf, tf);
			} else {
				FileUtils.copyFile(sf, tf);
			}
		}
		return new ReplyInfo<Object>();
	}

	@Override
	public ReplyInfo<Object> copy2(String from, String to, List<String> ipLt, int port) throws Throwable {
		for (String ip : ipLt) {
			this.copy2(from, to, ip, port);
		}
		return new ReplyInfo<Object>(true, "");
	}

	@Override
	public void write2(int index, String to, byte[] buf, int off, int len) throws Throwable {
		File f = new File(new StringBuilder().append(AgentConfig.FILE_PATH)//
				.append("/").append(to).toString());
		if (index == 0) {
			log.info("Writing file {}", f.getAbsolutePath());
		}
		FileUtil.makePath(f.getParent()); // 创建目录
		BufferedOutputStream bos = null;
		try {
			bos = new BufferedOutputStream(new FileOutputStream(f, index > 0));
			bos.write(buf, off, len);
		} finally {
			StreamUtil.close(bos);
		}
	}

	@Override
	public boolean exists(String path) {
		log.info("Check if file {} exists", path);
		return new File(path).exists();
	}

	@Override
	public void copy2(String from, String to, String addr) throws Throwable {
		log.info("Copy file {} to {} {}", from, addr, to);
		BufferedInputStream bis = null;
		RpcContext.specify2(addr); // 指定服务器
		try {
			bis = new BufferedInputStream(new FileInputStream(from));
			byte[] buf = new byte[40960];
			int len = 0;
			for (int i = 0; (len = bis.read(buf)) > 0; i++) {
				agentX.write2(i, to, buf, 0, len);
			}
		} finally {
			StreamUtil.close(bis);
			RpcContext.specify2(null);
		}
	}

	@Override
	public void copy2(String from, String to, String ip, int port) throws Throwable {
		String addr = new StringBuilder().append(ip).append(":").append(port).toString();
		this.copy2(from, to, addr);
	}

	@Override
	public ReplyInfo<Object> executeCmd(String path, String[] args) throws IOException, InterruptedException {
		return this.executeShell(AgentConfig.SHELL_PATH, path, args);
	}

	@Override
	public ReplyInfo<Object> executeShell(String path, String[] args) throws IOException, InterruptedException {
		return this.executeShell(AgentConfig.FILE_PATH, path, args);
	}

	@Override
	public ReplyInfo<Object> executeShell(String parent, String path, String[] args) throws IOException, InterruptedException {
		log.info("Execute shell {} with {}", path, args);
		StringBuilder sb = new StringBuilder();
		if (windows) {
			sb.append(parent).append("/").append(path);
			if (args != null) {
				for (String a : args) {
					sb.append(" ").append(a);
				}
			}
		} else {
			sb.append("sh ").append(parent).append("/").append(path);
			if (args != null) {
				for (String a : args) {
					sb.append(" ").append(a);
				}
			}
		}
		String cmd = sb.toString();
		return this.executeShell(cmd);
	}

	@Override
	public ReplyInfo<Object> executeShell(String cmd) throws IOException, InterruptedException {
		if (windows) {
			// log.debug("Execute shell: [start /c '{}']", cmd);
			return this.executeShell(new String[] { cmd });
		} else {
			log.debug("Execute shell: [sh -c '{}']", cmd);
			return this.executeShell(new String[] { "sh", "-c", cmd });
		}
	}

	@Override
	public ReplyInfo<Object> unzip(String sp, String tp) throws IOException {
		sp = new StringBuilder().append(AgentConfig.FILE_PATH)//
				.append("/").append(sp).toString();
		File sf = new File(sp);
		log.info("Unzip {} to {}", sp, tp);
		// 如目标目录存在则删除
		if (new File(tp).exists()) {
			if (!FileUtil.deleteDirectory(tp)) {
				return new ReplyInfo<Object>(//
						"Can not delete exists file path ".concat(tp));
			}
			if (!FileUtil.makePath(tp)) {
				return new ReplyInfo<Object>(//
						"Can not make file path ".concat(tp));
			}
		}
		ZipUtil.unzip(sf, tp);
		return new ReplyInfo<Object>();
	}

	@Override
	public ReplyInfo<Object> delete(String path) throws IOException {
		File f = new File(path);
		if (f.isDirectory()) {
			FileUtils.deleteDirectory(path);
			return new ReplyInfo<Object>();
		}
		if (f.delete()) {
			return new ReplyInfo<Object>();
		}
		return new ReplyInfo<Object>("Can not delete file {}", path);
	}

	public String getCharset() {
		return charset;
	}

	public void setCharset(String charset) {
		this.charset = charset;
	}

}